📌 Spring 기본 예외 처리 방식
Spring Boot에서 별도의 핸들링 없이 예외가 발생하면, DispatchServlet이 /error엔드포인트로 요청을 위임하고, BasicErrorController가 실행되고, 상태 코드 500 에러가 발생한다.
이때, BasicErrorController는 ErrorAttributes를 이용해 예외 정보를 가져와 JSON 또는 HTML로 응답한다.
BasicErrorController.java
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity<>(status);
}
Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
return new ResponseEntity<>(body, status);
}
}Response
{
"timestamp": "2025-02-27T05:28:50.861+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/"
}단점
- 사용자 친화적인 메시지를 제공할 수 없음
- 클라이언트가 에러에 대한 핸들링을 하기 힘듦
- 특정 예외 별 다른 응답을 제공하기 힘듦
위 단점을 해결하기 위해 Spring은 에러를 유연하게 처리할 수 있는 방법을 제공한다.
📌 @ExceptionHandler
✅ Exception 클래스 별 처리할 응답을 지정할 수 있다.
✅ 에러 응답의 payload를 자유롭게 다룰 수 있다.
MyController.java
@RestController
@RequestMapping
public class MyController {
@GetMapping
public String throwError() {
throw new CustomException();
}
@ExceptionHandler(CustomException.class)
public ResponseEntity<String> handleCustomException(final CustomException exception) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("bad request");
}
}Response
bad request동작 과정
1️⃣ 컨트롤러 실행 중 예외 발생 (throw new CustomException();)
2️⃣ DispatcherServlet이 예외를 감지하고 @ExceptionHandler 탐색
3️⃣ @ExceptionHandler(CustomException.class) 메서드가 실행됨
4️⃣ 예외가 ResponseEntity<String>로 변환되어 클라이언트에게 응답됨
장점
- 사용자 친화적인 메시지를 전달할 수 있다.
- 에러 코드나 payload를 통해 클라이언트가 에러 별 핸들링을 할 수 있다.
- 예외 별로 다른 응답을 제공할 수 있다.
📌 Spring Filter 예외 처리
문제점
Spring Filter는 서블릿 컨테이너 영역에서 관리되지 않기 때문에 Filter에서 발생한 에러는 ExceptionHandler가 감지할 수 없다.
CustomFilter.java
@Component
@Slf4j
public class CustomFilter implements Filter {
@Override
public void doFilter(final ServletRequest request, final ServletResponse response,
final FilterChain chain)
throws IOException, ServletException {
throw new CustomException();
}
}Response
{
"timestamp": "2025-02-27T05:49:57.566+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/"
}Filter에서 발생한 CustomException이 위에서 봤던 BasiErrorController로 전달된 것이다.
해결 방법
따라서 FIlter의 예외의 경우, 다음과 같이 따로 처리해줘야 한다.
CustomFilter.java
@Component
@Slf4j
public class CustomFilter implements Filter {
@Override
public void doFilter(final ServletRequest request, final ServletResponse response,
final FilterChain chain)
throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.sendError(HttpServletResponse.SC_BAD_REQUEST, "bad request");
}
}Response
{
"timestamp": "2025-02-27T05:57:10.784+00:00",
"status": 400,
"error": "Bad Request",
"path": "/"
}동작 과정
1️⃣ Filter 내부에서 httpResponse.sendError(HttpServletResponse.SC_BAD_REQUEST, "bad request") 호출
2️⃣ 서블릿 컨테이너가 400 상태 코드와 메시지를 포함한 요청을 다시 /error 엔드포인트로 전달
3️⃣ Spring Boot의 BasicErrorController가 /error 요청을 받아 JSON 응답 생성
4️⃣ 클라이언트가 JSON 응답을 받음
📌 참고
https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
https://mangkyu.tistory.com/173